<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <title>index</title>
</head>

<body>
<form method="post" action="/fs/upload" enctype="multipart/form-data">
    <label>files: <input id="files" type="file" name="files" multiple="multiple"></label><br/>
    <label>bucket: <input id="bucket" type="text" name="bucket" value="bucket1"></label><br/>
    <label>relativePath: <input id="relativePath" type="text" name="relativePath" value="test"></label>
    <input id="directSubmit" type="submit" hidden value="上传">
</form>
<button id="submit-http">http上传</button>
<br/><br/>
<button id="submit-ws">websocket上传</button>
<br/><br/><br/><br/>
<a href="/fs/data/">查看上传文件列表</a><br/><br/><br/>
<div id="upload-process"></div>
</body>
</html>
<script src="/fs/statics/js/jquery-3.6.3.min.js"></script>
<script src="/fs/statics/js/common.js"></script>
<script>
    let downloadUri = "/fs/api/download"
    let uploadUri = "/fs/upload"
    let sliceCheckUri = "/fs/api/sliceCheck"
    let ChunkSize = 4 * 1024 * 1024
    let SliceUploadThreshold = ChunkSize * 4// http分片上传阈值

    // 文件上传状态
    let UnUpload = 0;// 未上传
    let Uploading = 1;// 上传中
    let UploadSuccess = 2;// 上传成功
    let UploadFailed = 3;// 上传失败

    // 消息类型
    let Type_Checkpoint = 1;// 断点检查消息
    let Type_Ack = 2;// ack消息
    let Type_FileUploadStart = 3;// 文件开始传输消息：用于存储元信息key和打开fileChannel
    let Type_FileUploadFinish = 4;// 文件传输完成消息：用于更新元信息和关闭fileChannel
    let Type_Err = -1;// 错误提示消息

    const wsUri = "/fs/websocket"
    const wsPath = window.location.origin.replace("https", "wss").replace("http", "ws") + wsUri
    const MaxSocketCount = 3 // 最多创建的socket数量

    let jsUploadFlag = false // false为未上传，true为上传中
    let uploadingFileCount = 0 // 正在上传的文件数量
</script>
<!--http上传代码-->
<script>
    $(function () {
        $("#submit-http").click(function () {
            let files = document.getElementById("files").files
            if (files.length === 0) {
                alert("请选择需上传文件")
                return false
            }
            if (jsUploadFlag) {
                alert("当前正在上传中，请勿重复点击")
                return false
            }
            jsUploadFlag = true
            console.log("将上传以下文件:", files)
            // 1.判断大小
            let totalSize = 0
            for (let i = 0; i < files.length; i++) {
                let file = files[i]
                totalSize += file.size
            }
            console.log("待上传文件总数量: " + files.length + ", 总大小: " + totalSize)
            // 1.1 小于100MB直接上传
            if (totalSize < SliceUploadThreshold) {
                $("#directSubmit").click();
                //jsUploadFlag=false
                return true
            }
            // 1.2 超过的则每个文件都分片上传，每个文件一个ajax，并发
            for (let i = 0; i < files.length; i++) {
                let file = files[i]
                increaseUploadFileCount()
                checkPoint(file)// 找到断点并上传
            }
            return true
        })
    })

    // 检查断点
    function checkPoint(file) {
        let uri = sliceCheckUri
            + "?bucket=" + $("#bucket").val()
            + "&relativePath=" + $("#relativePath").val()
            + "&filename=" + file.name
            + "&size=" + file.size
        ajax(uri, "GET", null, function (result) {
            if (result.code !== 200) {
                alert(result.code + ": " + result.msg)
                return false
            }
            let ack = result.data.ack
            let key = result.data.key
            let uploadStatus = result.data.uploadStatus
            if (ack < 0) {
                alert("断点检查返回错误断点为: " + ack)
                return false;
            }
            // 添加一个div用于显示文件分片上传进度
            $("#upload-process").prepend('<div id="' + key + '"></div><br/>')
            if (uploadStatus === UploadSuccess) {
                if (file.size !== ack) {
                    $("#" + key).html('文件已经存在，且两者大小不一致，若需要新上传请删除原文件: filename=' + file.name + ', key=' + key + ', url=' + '<a href="' + downloadUri + '?key=' + key + '" target="_blank">' + window.location.origin + downloadUri + '?key=' + key + '</a>')
                } else {
                    $("#" + key).html('断点检查, 文件无须上传: filename=' + file.name + ', key=' + key + ', url=' + '<a href="' + downloadUri + '?key=' + key + '" target="_blank">' + window.location.origin + downloadUri + '?key=' + key + '</a>')
                }
                decreaseUploadFileCount()
                return false
            } else {
                if (ack !== 0) {
                    $("#" + key).text("断点续传: position=" + ack + ", key=" + key)
                }
                http_partitionUpload(file, ack, ChunkSize)// 分片上传
            }
        })
    }

    // 分片上传
    function http_partitionUpload(file, position, chunkSize) {
        if (position >= file.size) {
            console.log("文件: " + file.name + " 已上传完成")
            decreaseUploadFileCount()
            return false
        }

        let form = new FormData()
        form.append("files", file.slice(position, position + chunkSize))// 文件切片
        form.append("bucket", $("#bucket").val())
        form.append("relativePath", $("#relativePath").val())
        form.append("filename", file.name)
        form.append("position", position)
        form.append("size", file.size)// 文件总大小
        ajax(uploadUri, "POST", form, function (result) {
            console.log(result)
            if (result.code !== 200) {
                alert(result.code + ": " + result.msg)
                return false
            }
            let ack = result.data.ack
            let key = result.data.key
            let process = (ack / file.size * 100).toString().substring(0, 5)// 保留前两位小数
            $("#" + key).html('分片上传: filename=' + file.name + ', key=' + key + ', progress=' + process + '%, url=' + '<a href="' + downloadUri + '?key=' + key + '" target="_blank">' + window.location.origin + downloadUri + '?key=' + key + '</a>')
            http_partitionUpload(file, ack, chunkSize)
        })
    }
</script>
<script>
    function increaseUploadFileCount() {
        if (uploadingFileCount === 0) {
            jsUploadFlag = true
        }
        uploadingFileCount++
    }

    function decreaseUploadFileCount() {
        if (uploadingFileCount > 0) {
            uploadingFileCount--
            if (uploadingFileCount === 0) {
                jsUploadFlag = false
            }
        }
    }
</script>
<!--websocket上传代码-->
<script>
    // 判断浏览器是否支持websocket
    if (!window.WebSocket) {
        window.WebSocket = window.MozWebSocket;
    }
    if (!window.WebSocket) {
        alert("Your browser does not support Web Socket.");
    }

    function send(socket, message) {
        if (!window.WebSocket) {
            alert("Your browser does not support Web Socket.");
            return;
        }
        if (socket.readyState === WebSocket.OPEN) {
            socket.send(message);
        } else {
            alert("The socket is not open.");
        }
    }

    $(function () {
        $("#submit-ws").click(function () {
            let files = document.getElementById("files").files
            if (files.length === 0) {
                alert("请选择需上传文件")
                return false
            }

            if (jsUploadFlag) {
                alert("当前正在上传中，请勿重复点击")
                return false
            }
            jsUploadFlag = true

            let bucket = document.getElementById("bucket").value
            let relativePath = document.getElementById("relativePath").value
            console.log("bucket: " + bucket + ", relativePath: " + relativePath + ", 将上传以下文件:", files)

            let curIndex = 0;// 当前上传到文件的哪个idx
            // 最多创建3个socket，避免对后端产生较大压力
            let socketCount = files.length > MaxSocketCount ? MaxSocketCount : files.length
            for (let i = 0; i < socketCount; i++) {
                let socket = new WebSocket(wsPath)
                socket.onopen = function () {
                    console.log("websocket opened")
                    // 发送检查断点消息
                    tryTransferNextFile(socket)
                }
            }

            function tryTransferNextFile(socket) {
                if (curIndex >= files.length) {
                    socket.close() // 关闭ws
                    return false
                }
                // 下个传输的文件
                let file = files[curIndex++]
                transferFile(socket, file)
            }

            function transferFile(socket, file) {
                increaseUploadFileCount()
                socket.onmessage = function (event) {
                    let wsMsg = JSON.parse(event.data);
                    console.log(wsMsg)
                    // 断点检查回复
                    if (wsMsg.type === Type_Checkpoint) {
                        let ack = wsMsg.body.ack
                        let key = wsMsg.body.key
                        let uploadStatus = wsMsg.body.uploadStatus
                        $("#upload-process").prepend('<div id="' + key + '"></div><br/>')
                        if (uploadStatus === UploadSuccess) {
                            if (ack === file.size) {
                                $("#" + key).html('断点检查, 文件无须上传: filename=' + file.name + ', key=' + key + ', url=' + '<a href="' + downloadUri + '?key=' + key + '" target="_blank">' + window.location.origin + downloadUri + '?key=' + key + '</a>')
                            } else {
                                $("#" + key).html('文件已经存在，且两者大小不一致，若需要新上传请删除原文件: filename=' + file.name + ', key=' + key + ', url=' + '<a href="' + downloadUri + '?key=' + key + '" target="_blank">' + window.location.origin + downloadUri + '?key=' + key + '</a>')
                            }
                            decreaseUploadFileCount()
                            tryTransferNextFile(socket)
                            return false
                        } else if (ack > 0) {
                            $("#" + key).text("断点续传: position=" + ack + ", key=" + key)
                        } else if (ack === 0) {
                            $("#" + key).text("开始上传: key=" + key)
                        }
                        let startMsg = {
                            "type": Type_FileUploadStart,
                            "headers": {
                                "bucket": bucket,
                                "relativePath": relativePath,
                                "filename": file.name,
                                "size": file.size,
                                "position": ack,
                                "key": key,
                            }
                        }
                        // 发个开始消息并开始上传
                        send(socket, JSON.stringify(startMsg))
                        ws_partitionUpload(socket, file, ack, ChunkSize)
                    } else if (wsMsg.type === Type_Ack) {
                        let ack = wsMsg.headers.ack
                        console.log(ack / ChunkSize)

                        let key = wsMsg.headers.key
                        let process = (ack / file.size * 100).toString().substring(0, 5)// 保留前两位小数
                        $("#" + key).html('分片上传: filename=' + file.name + ', key=' + key + ', progress=' + process + '%, url=' + '<a href="' + downloadUri + '?key=' + key + '" target="_blank">' + window.location.origin + downloadUri + '?key=' + key + '</a>')
                        if (ack < file.size) {
                            ws_partitionUpload(socket, file, ack, ChunkSize)
                        } else {
                            let finishMsg = {
                                "type": Type_FileUploadFinish,
                                "headers": {
                                    "bucket": bucket,
                                    "relativePath": relativePath,
                                    "filename": file.name,
                                    "size": file.size,
                                    "position": file.size,
                                    "key": key,
                                },
                            }
                            // 发个结束消息用于后台落盘
                            send(socket, JSON.stringify(finishMsg))
                            decreaseUploadFileCount()
                            console.log("上传结束, key: " + key + ", filename: " + file.name)
                            tryTransferNextFile(socket)
                        }
                    } else if (wsMsg.type === Type_Err) {
                        socket.close()
                        console.error(wsMsg)
                        alert("错误: " + wsMsg)
                    } else {
                        socket.close()
                        console.error(wsMsg)
                        alert("未知消息: " + wsMsg)
                    }
                };
                // socket.onopen = function () {
                //     console.log("websocket opened")
                //     // 发送检查断点消息
                //     checkpoint(socket, bucket, relativePath, file)
                // };
                //发生了错误事件
                socket.onerror = function (event) {
                    console.error("websocket发生了错误")
                    console.error(event)
                    socket.close()
                }
                socket.onclose = function (event) {
                    console.log("websocket closed")
                    console.log(event)
                };

                // 发送断点检查消息作为传输文件开头
                checkpoint(socket, bucket, relativePath, file)
            }
        })
    })

    function checkpoint(socket, bucket, relativePath, file) {
        let checkMsg = {
            "type": Type_Checkpoint,
            "headers": {
                "bucket": bucket,
                "relativePath": relativePath,
                "filename": file.name,
                "size": file.size,
            },
        }
        send(socket, JSON.stringify(checkMsg))
    }

    function ws_partitionUpload(socket, file, position, chunkSize) {
        if (position >= file.size) {
            return false
        }
        console.log("分片上传，filename: " + file.name + ", position: " + position)
        let slice = file.slice(position, position + chunkSize);
        send(socket, slice)
    }
</script>
